{"componentChunkName":"component---src-templates-blog-post-blog-post-js","path":"/articulos/que-es-rag-inteligencia-artificial/","result":{"data":{"mdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"Qué es RAG en inteligencia artificial y cómo integrarlo en una aplicación web\",\n  \"date\": \"2026-10-28\",\n  \"description_sort\": \"Aprende cómo funciona RAG, desde los fragmentos y embeddings hasta su integración con NestJS y PostgreSQL.\",\n  \"description\": \"Descubre qué es RAG, cómo funcionan los embeddings y las bases de datos vectoriales y cómo integrar esta arquitectura en una aplicación web con NestJS y PostgreSQL.\",\n  \"featuredImage\": \"rag-portada.webp\",\n  \"twitterUsername\": \"@CursosDesarrol1\",\n  \"tags\": [\"inteligencia artificial\", \"nodejs\", \"postgresql\"],\n  \"keywords\": [\"qué es RAG\", \"RAG inteligencia artificial\", \"Retrieval Augmented Generation\", \"arquitectura RAG\", \"RAG con LLM\", \"embeddings\", \"bases de datos vectoriales\", \"RAG NestJS\", \"RAG PostgreSQL\", \"pgvector\"]\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h1\", null, \"Qu\\xE9 es RAG en inteligencia artificial y c\\xF3mo integrarlo en una aplicaci\\xF3n web\"), mdx(\"p\", null, mdx(\"span\", {\n    parentName: \"p\",\n    \"className\": \"gatsby-resp-image-wrapper\",\n    \"style\": {\n      \"position\": \"relative\",\n      \"display\": \"block\",\n      \"marginLeft\": \"auto\",\n      \"marginRight\": \"auto\",\n      \"maxWidth\": \"1200px\"\n    }\n  }, \"\\n      \", mdx(\"a\", {\n    parentName: \"span\",\n    \"className\": \"gatsby-resp-image-link\",\n    \"href\": \"/static/55cfbb00f1817386da756c2a87abe1c8/5bb53/rag-portada.webp\",\n    \"style\": {\n      \"display\": \"block\"\n    },\n    \"target\": \"_blank\",\n    \"rel\": \"noopener\"\n  }, \"\\n    \", mdx(\"span\", {\n    parentName: \"a\",\n    \"className\": \"gatsby-resp-image-background-image\",\n    \"style\": {\n      \"paddingBottom\": \"43.333333333333336%\",\n      \"position\": \"relative\",\n      \"bottom\": \"0\",\n      \"left\": \"0\",\n      \"backgroundImage\": \"url('data:image/webp;base64,UklGRnAAAABXRUJQVlA4IGQAAACQAwCdASoUAAkAPtFUo0uoJKMhsAgBABoJYwC7ABtZiwI8B8oAAP7tq0HapumGlZYcZYvAnn00ryyPqR6hntsvYeyadneSzQx01VpUVHrQuKHLrrWecmg6Ai9cVIFAZPZPgAAA')\",\n      \"backgroundSize\": \"cover\",\n      \"display\": \"block\"\n    }\n  }), \"\\n  \", mdx(\"img\", {\n    parentName: \"a\",\n    \"className\": \"gatsby-resp-image-image\",\n    \"alt\": \"Ilustración del proceso RAG: pregunta, documentos, recuperación y respuesta\",\n    \"title\": \"Ilustración del proceso RAG: pregunta, documentos, recuperación y respuesta\",\n    \"src\": \"/static/55cfbb00f1817386da756c2a87abe1c8/5bb53/rag-portada.webp\",\n    \"srcSet\": [\"/static/55cfbb00f1817386da756c2a87abe1c8/cb58d/rag-portada.webp 300w\", \"/static/55cfbb00f1817386da756c2a87abe1c8/f8b1b/rag-portada.webp 600w\", \"/static/55cfbb00f1817386da756c2a87abe1c8/5bb53/rag-portada.webp 1200w\"],\n    \"sizes\": \"(max-width: 1200px) 100vw, 1200px\",\n    \"style\": {\n      \"width\": \"100%\",\n      \"height\": \"100%\",\n      \"margin\": \"0\",\n      \"verticalAlign\": \"middle\",\n      \"position\": \"absolute\",\n      \"top\": \"0\",\n      \"left\": \"0\"\n    },\n    \"loading\": \"lazy\"\n  }), \"\\n  \"), \"\\n    \")), mdx(\"p\", null, \"Imaginemos que estamos desarrollando una aplicaci\\xF3n de gesti\\xF3n empresarial y queremos incorporar un asistente de inteligencia artificial.\"), mdx(\"p\", null, \"El usuario podr\\xEDa preguntarle:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"\\xBFCu\\xE1l es el procedimiento para solicitar una modificaci\\xF3n de un registro?\")), mdx(\"p\", null, \"O quiz\\xE1 algo m\\xE1s espec\\xEDfico:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"Seg\\xFAn la documentaci\\xF3n interna de mi empresa, \\xBFqui\\xE9n puede aprobar una solicitud de modificaci\\xF3n?\")), mdx(\"p\", null, \"Un modelo de lenguaje como GPT, Claude o Gemini puede responder preguntas generales, generar c\\xF3digo o explicar conceptos t\\xE9cnicos. Sin embargo, no conoce necesariamente la documentaci\\xF3n privada de nuestra aplicaci\\xF3n.\"), mdx(\"p\", null, \"Tampoco tiene por qu\\xE9 conocer los procedimientos internos de una empresa, las \\xFAltimas modificaciones de un manual o el contenido de una base de datos que nunca ha visto.\"), mdx(\"p\", null, \"Podr\\xEDamos intentar resolverlo enviando toda esa informaci\\xF3n junto con cada pregunta.\"), mdx(\"p\", null, \"Pero \\xBFqu\\xE9 ocurre cuando tenemos cientos de documentos, miles de p\\xE1ginas o millones de registros?\"), mdx(\"p\", null, \"Aqu\\xED entra en juego \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"RAG, una de las arquitecturas m\\xE1s utilizadas para construir aplicaciones de inteligencia artificial que necesitan trabajar con informaci\\xF3n propia\"), \".\"), mdx(\"p\", null, \"En este art\\xEDculo veremos qu\\xE9 es, c\\xF3mo funciona internamente y c\\xF3mo podemos integrarlo en una aplicaci\\xF3n web utilizando tecnolog\\xEDas habituales como Node.js, NestJS y PostgreSQL.\"), mdx(\"h2\", null, \"\\xBFQu\\xE9 es RAG?\"), mdx(\"p\", null, \"RAG son las siglas de \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Retrieval-Augmented Generation\"), \", que podemos traducir como \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"generaci\\xF3n aumentada mediante recuperaci\\xF3n de informaci\\xF3n\"), \".\"), mdx(\"p\", null, \"La idea es relativamente sencilla.\"), mdx(\"p\", null, \"En lugar de pedirle a un modelo de inteligencia artificial que responda \\xFAnicamente utilizando el conocimiento adquirido durante su entrenamiento, primero buscamos informaci\\xF3n relevante en una fuente externa.\"), mdx(\"p\", null, \"Despu\\xE9s proporcionamos esa informaci\\xF3n al modelo para que construya su respuesta.\"), mdx(\"p\", null, \"El proceso se divide en tres operaciones:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Retrieval:\"), \" recuperar informaci\\xF3n relevante.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Augmentation:\"), \" incorporar esa informaci\\xF3n al contexto del modelo.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Generation:\"), \" generar una respuesta utilizando ese contexto.\")), mdx(\"p\", null, \"Por ejemplo, si un usuario pregunta:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"\\xBFCu\\xE1nto tiempo se conservan los registros de actividad en nuestra aplicaci\\xF3n?\")), mdx(\"p\", null, \"El sistema puede buscar esa informaci\\xF3n en la documentaci\\xF3n interna y recuperar un fragmento como este:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Los registros de actividad se conservan durante\\ncuatro a\\xF1os desde la fecha de su creaci\\xF3n.\\n\"))), mdx(\"p\", null, \"A continuaci\\xF3n enviamos al modelo tanto la pregunta como el fragmento recuperado.\"), mdx(\"p\", null, \"El modelo ya no necesita inventar la respuesta ni depender exclusivamente de su entrenamiento.\"), mdx(\"p\", null, \"Dispone de informaci\\xF3n espec\\xEDfica sobre nuestra aplicaci\\xF3n.\"), mdx(\"p\", null, \"Esta es la idea fundamental de RAG.\"), mdx(\"h2\", null, \"\\xBFPor qu\\xE9 no basta con utilizar directamente un LLM?\"), mdx(\"p\", null, \"Para comprender la utilidad de RAG debemos distinguir entre el conocimiento del modelo y los datos de nuestra aplicaci\\xF3n.\"), mdx(\"p\", null, \"Un LLM ha aprendido patrones y relaciones durante su entrenamiento.\"), mdx(\"p\", null, \"Ese conocimiento est\\xE1 representado en sus par\\xE1metros.\"), mdx(\"p\", null, \"Pero los par\\xE1metros del modelo no funcionan como una base de datos convencional a la que podamos a\\xF1adir registros individualmente.\"), mdx(\"p\", null, \"Si ma\\xF1ana modificamos una pol\\xEDtica interna de nuestra empresa, el modelo no conoce autom\\xE1ticamente ese cambio.\"), mdx(\"p\", null, \"Y aunque pudiera responder utilizando informaci\\xF3n general, eso no garantiza que su respuesta coincida con nuestras reglas particulares.\"), mdx(\"p\", null, \"Supongamos que desarrollamos una plataforma SaaS.\"), mdx(\"p\", null, \"Nuestra aplicaci\\xF3n contiene:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"documentaci\\xF3n funcional;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"manuales de usuario;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"procedimientos internos;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"condiciones de contrataci\\xF3n;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"preguntas frecuentes;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"documentaci\\xF3n t\\xE9cnica.\")), mdx(\"p\", null, \"Queremos que un asistente responda preguntas utilizando esos documentos.\"), mdx(\"p\", null, \"Sin RAG, tendr\\xEDamos principalmente dos alternativas.\"), mdx(\"p\", null, \"La primera ser\\xEDa incluir toda la documentaci\\xF3n en el prompt.\"), mdx(\"p\", null, \"La segunda, entrenar o adaptar un modelo con informaci\\xF3n espec\\xEDfica.\"), mdx(\"p\", null, \"Ambas presentan limitaciones.\"), mdx(\"p\", null, \"Enviar toda la documentaci\\xF3n puede consumir demasiados tokens y aumentar el coste y la latencia. Adem\\xE1s, disponer de una ventana de contexto grande no garantiza que el modelo utilice correctamente toda la informaci\\xF3n.\"), mdx(\"p\", null, \"Por otro lado, ajustar un modelo mediante fine-tuning no suele ser la soluci\\xF3n apropiada cuando simplemente necesitamos consultar documentos actualizados.\"), mdx(\"p\", null, \"RAG proporciona una alternativa: \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"mantener la informaci\\xF3n fuera del modelo y recuperarla cuando sea necesaria\"), \".\"), mdx(\"h2\", null, \"C\\xF3mo funciona la arquitectura de un RAG\"), mdx(\"p\", null, \"Un sistema RAG suele dividirse en dos procesos diferentes.\"), mdx(\"p\", null, \"El primero prepara e indexa la informaci\\xF3n.\"), mdx(\"p\", null, \"El segundo recupera informaci\\xF3n y genera respuestas cuando el usuario realiza una consulta.\"), mdx(\"h3\", null, \"Primera fase: preparar los documentos\"), mdx(\"p\", null, \"Imaginemos que disponemos de una colecci\\xF3n de archivos:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"documentos/\\n  manual-usuario.pdf\\n  preguntas-frecuentes.md\\n  politica-seguridad.pdf\\n  procedimientos.docx\\n\"))), mdx(\"p\", null, \"No resulta eficiente enviar todos esos documentos al modelo cada vez que alguien hace una pregunta.\"), mdx(\"p\", null, \"Necesitamos preparar un \\xEDndice que nos permita localizar r\\xE1pidamente los fragmentos relacionados con cada consulta.\"), mdx(\"p\", null, \"El proceso habitual es:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Documentos\\n     |\\n     v\\nExtracci\\xF3n de texto\\n     |\\n     v\\nDivisi\\xF3n en fragmentos\\n     |\\n     v\\nGeneraci\\xF3n de embeddings\\n     |\\n     v\\nBase de datos vectorial\\n\"))), mdx(\"p\", null, \"Veamos qu\\xE9 significa cada etapa.\"), mdx(\"h2\", null, \"Chunking: dividir la informaci\\xF3n en fragmentos\"), mdx(\"p\", null, mdx(\"span\", {\n    parentName: \"p\",\n    \"className\": \"gatsby-resp-image-wrapper\",\n    \"style\": {\n      \"position\": \"relative\",\n      \"display\": \"block\",\n      \"marginLeft\": \"auto\",\n      \"marginRight\": \"auto\",\n      \"maxWidth\": \"1200px\"\n    }\n  }, \"\\n      \", mdx(\"a\", {\n    parentName: \"span\",\n    \"className\": \"gatsby-resp-image-link\",\n    \"href\": \"/static/76423b76bf0bcc1189a4fb3357fbf160/5bb53/rag-fragmentos.webp\",\n    \"style\": {\n      \"display\": \"block\"\n    },\n    \"target\": \"_blank\",\n    \"rel\": \"noopener\"\n  }, \"\\n    \", mdx(\"span\", {\n    parentName: \"a\",\n    \"className\": \"gatsby-resp-image-background-image\",\n    \"style\": {\n      \"paddingBottom\": \"50%\",\n      \"position\": \"relative\",\n      \"bottom\": \"0\",\n      \"left\": \"0\",\n      \"backgroundImage\": \"url('data:image/webp;base64,UklGRk4AAABXRUJQVlA4IEIAAAAwAwCdASoUAAoAPtFUo0uoJKMhsAgBABoJZwC7ADKMDiQAAP7wwG9WD0qSaQ3xupkphvOm349r0PSYyofw+VEAAAA=')\",\n      \"backgroundSize\": \"cover\",\n      \"display\": \"block\"\n    }\n  }), \"\\n  \", mdx(\"img\", {\n    parentName: \"a\",\n    \"className\": \"gatsby-resp-image-image\",\n    \"alt\": \"Un documento se divide en fragmentos que se agrupan por significado\",\n    \"title\": \"Un documento se divide en fragmentos que se agrupan por significado\",\n    \"src\": \"/static/76423b76bf0bcc1189a4fb3357fbf160/5bb53/rag-fragmentos.webp\",\n    \"srcSet\": [\"/static/76423b76bf0bcc1189a4fb3357fbf160/cb58d/rag-fragmentos.webp 300w\", \"/static/76423b76bf0bcc1189a4fb3357fbf160/f8b1b/rag-fragmentos.webp 600w\", \"/static/76423b76bf0bcc1189a4fb3357fbf160/5bb53/rag-fragmentos.webp 1200w\"],\n    \"sizes\": \"(max-width: 1200px) 100vw, 1200px\",\n    \"style\": {\n      \"width\": \"100%\",\n      \"height\": \"100%\",\n      \"margin\": \"0\",\n      \"verticalAlign\": \"middle\",\n      \"position\": \"absolute\",\n      \"top\": \"0\",\n      \"left\": \"0\"\n    },\n    \"loading\": \"lazy\"\n  }), \"\\n  \"), \"\\n    \")), mdx(\"p\", null, \"El primer concepto importante es el \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"chunking\"), \".\"), mdx(\"p\", null, \"Consiste en dividir documentos grandes en fragmentos m\\xE1s peque\\xF1os que puedan recuperarse individualmente.\"), mdx(\"p\", null, \"Imaginemos un manual de doscientas p\\xE1ginas.\"), mdx(\"p\", null, \"Un usuario pregunta:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"\\xBFC\\xF3mo puedo restablecer mi contrase\\xF1a?\")), mdx(\"p\", null, \"No necesitamos enviarle al modelo las doscientas p\\xE1ginas.\"), mdx(\"p\", null, \"Necesitamos localizar la secci\\xF3n que explica el procedimiento de recuperaci\\xF3n.\"), mdx(\"p\", null, \"Para conseguirlo podemos dividir el documento en fragmentos.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Documento: Manual de usuario\\n\\nFragmento 1:\\nIntroducci\\xF3n y descripci\\xF3n de la aplicaci\\xF3n.\\n\\nFragmento 2:\\nProceso de registro de usuarios.\\n\\nFragmento 3:\\nRecuperaci\\xF3n de contrase\\xF1a.\\n\\nFragmento 4:\\nConfiguraci\\xF3n del perfil.\\n\"))), mdx(\"p\", null, \"Cada fragmento puede almacenarse con informaci\\xF3n adicional:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-json\"\n  }, \"{\\n  \\\"documentId\\\": 12,\\n  \\\"chunkIndex\\\": 3,\\n  \\\"title\\\": \\\"Recuperaci\\xF3n de contrase\\xF1a\\\",\\n  \\\"content\\\": \\\"Para recuperar la contrase\\xF1a...\\\",\\n  \\\"source\\\": \\\"manual-usuario.pdf\\\"\\n}\\n\"))), mdx(\"p\", null, \"Esta informaci\\xF3n adicional se denomina \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"metadata\"), \".\"), mdx(\"p\", null, \"Nos permite identificar el origen del fragmento, aplicar filtros y construir referencias a los documentos originales.\"), mdx(\"h3\", null, \"\\xBFQu\\xE9 tama\\xF1o deben tener los fragmentos?\"), mdx(\"p\", null, \"No existe un tama\\xF1o universal.\"), mdx(\"p\", null, \"Los fragmentos peque\\xF1os permiten localizar informaci\\xF3n precisa, pero pueden perder contexto.\"), mdx(\"p\", null, \"Los fragmentos grandes conservan mejor las relaciones entre ideas, aunque pueden introducir informaci\\xF3n irrelevante.\"), mdx(\"p\", null, \"Tambi\\xE9n podemos utilizar solapamiento entre fragmentos para evitar cortar una explicaci\\xF3n justo en un l\\xEDmite.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Chunk 1: p\\xE1rrafos 1, 2 y 3\\nChunk 2: p\\xE1rrafos 3, 4 y 5\\nChunk 3: p\\xE1rrafos 5, 6 y 7\\n\"))), mdx(\"p\", null, \"En sistemas m\\xE1s avanzados podemos dividir por encabezados, secciones o unidades sem\\xE1nticas en lugar de utilizar \\xFAnicamente un n\\xFAmero fijo de caracteres.\"), mdx(\"p\", null, \"La estrategia de chunking afecta directamente a la calidad del RAG.\"), mdx(\"h2\", null, \"Embeddings: convertir texto en vectores\"), mdx(\"p\", null, \"El siguiente concepto fundamental son los \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"embeddings\"), \".\"), mdx(\"p\", null, \"Un embedding es una representaci\\xF3n num\\xE9rica de un contenido.\"), mdx(\"p\", null, \"En el caso del texto, utilizamos un modelo especializado para transformar una frase o documento en un vector de n\\xFAmeros.\"), mdx(\"p\", null, \"Por ejemplo, una frase como:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"El usuario puede recuperar su contrase\\xF1a\\nmediante un enlace enviado por correo.\\n\"))), mdx(\"p\", null, \"podr\\xEDa transformarse en algo conceptualmente similar a:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-json\"\n  }, \"[0.12, -0.43, 0.87, 0.21, -0.08]\\n\"))), mdx(\"p\", null, \"Este vector es \\xFAnicamente ilustrativo. Los modelos reales suelen generar vectores con muchas m\\xE1s dimensiones.\"), mdx(\"p\", null, \"\\xBFPara qu\\xE9 sirve?\"), mdx(\"p\", null, \"La idea es que textos relacionados sem\\xE1nticamente tengan representaciones pr\\xF3ximas dentro de un espacio vectorial.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"\\xBFC\\xF3mo recupero mi contrase\\xF1a?\\n\\nHe olvidado mi clave de acceso.\\n\\nNo puedo entrar porque no recuerdo mi password.\\n\"))), mdx(\"p\", null, \"Aunque las tres frases utilizan palabras diferentes, expresan necesidades similares.\"), mdx(\"p\", null, \"Un modelo de embeddings puede representar esa relaci\\xF3n.\"), mdx(\"p\", null, \"Esto permite buscar informaci\\xF3n no solamente por coincidencias exactas de palabras, sino tambi\\xE9n por similitud sem\\xE1ntica.\"), mdx(\"h2\", null, \"\\xBFQu\\xE9 es una base de datos vectorial?\"), mdx(\"p\", null, \"Una vez generados los embeddings necesitamos almacenarlos y consultarlos.\"), mdx(\"p\", null, \"Podemos utilizar motores especializados como Qdrant, Weaviate o Milvus.\"), mdx(\"p\", null, \"Tambi\\xE9n podemos utilizar bases de datos tradicionales que incorporan capacidades vectoriales.\"), mdx(\"p\", null, \"Un ejemplo especialmente interesante para desarrolladores backend es PostgreSQL con la extensi\\xF3n \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"pgvector\"), \".\"), mdx(\"p\", null, \"Supongamos que queremos almacenar fragmentos de documentaci\\xF3n.\"), mdx(\"p\", null, \"Podr\\xEDamos disponer de una tabla como esta:\"), mdx(CodeJs, {\n    language: \"sql\",\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-sql\"\n  }, \"CREATE EXTENSION IF NOT EXISTS vector;\\n\\nCREATE TABLE document_chunks (\\n    id BIGSERIAL PRIMARY KEY,\\n    document_id BIGINT NOT NULL,\\n    content TEXT NOT NULL,\\n    embedding vector(1536),\\n    created_at TIMESTAMPTZ DEFAULT NOW()\\n);\\n\"))), mdx(\"p\", null, \"El campo \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"embedding\"), \" almacena el vector del fragmento.\"), mdx(\"p\", null, \"En este ejemplo utilizamos 1536 dimensiones, pero ese valor debe coincidir con el modelo de embeddings elegido.\"), mdx(\"p\", null, \"La consulta ya no consiste necesariamente en buscar palabras mediante \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LIKE\"), \".\"), mdx(\"p\", null, \"Podemos buscar los vectores m\\xE1s pr\\xF3ximos al embedding de una pregunta.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(CodeJs, {\n    language: \"sql\",\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-sql\"\n  }, \"SELECT\\n    id,\\n    document_id,\\n    content\\nFROM document_chunks\\nORDER BY embedding <=> $1::vector\\nLIMIT 5;\\n\"))), mdx(\"p\", null, \"En pgvector, el operador \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"<=>\"), \" calcula la distancia coseno entre dos vectores.\"), mdx(\"p\", null, \"La consulta devuelve los cinco fragmentos m\\xE1s pr\\xF3ximos seg\\xFAn esa m\\xE9trica.\"), mdx(\"p\", null, \"Y esos fragmentos ser\\xE1n los candidatos que utilizaremos como contexto para el modelo.\"), mdx(\"p\", null, \"En una aplicaci\\xF3n real debemos incorporar filtrado de permisos, control de documentos vigentes e \\xEDndices apropiados cuando el volumen de informaci\\xF3n lo justifique.\"), mdx(\"h2\", null, \"Segunda fase: responder las preguntas\"), mdx(\"p\", null, \"Ya tenemos nuestros documentos indexados.\"), mdx(\"p\", null, \"Ahora un usuario realiza una pregunta:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"\\xBFC\\xF3mo puedo recuperar mi contrase\\xF1a?\")), mdx(\"p\", null, \"El backend ejecuta un proceso parecido al siguiente:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Usuario\\n   |\\n   v\\nPregunta\\n   |\\n   v\\nGeneraci\\xF3n del embedding\\n   |\\n   v\\nB\\xFAsqueda vectorial\\n   |\\n   v\\nFragmentos relevantes\\n   |\\n   v\\nConstrucci\\xF3n del prompt\\n   |\\n   v\\nLLM\\n   |\\n   v\\nRespuesta\\n\"))), mdx(\"p\", null, \"El backend utiliza el mismo modelo de embeddings, o uno compatible con el \\xEDndice, para convertir la pregunta en un vector.\"), mdx(\"p\", null, \"Despu\\xE9s consulta la base de datos vectorial y recupera los fragmentos relevantes.\"), mdx(\"p\", null, \"Finalmente construye el contexto que enviar\\xE1 al LLM.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Eres un asistente de soporte t\\xE9cnico.\\n\\nResponde utilizando \\xFAnicamente la informaci\\xF3n\\nproporcionada en el contexto.\\n\\nSi la informaci\\xF3n no es suficiente,\\nind\\xEDcalo claramente.\\n\\nCONTEXTO:\\n\\nDocumento: Manual de usuario\\n\\nPara recuperar una contrase\\xF1a, el usuario debe\\nseleccionar \\\"He olvidado mi contrase\\xF1a\\\" e\\nintroducir su correo electr\\xF3nico.\\n\\nEl sistema enviar\\xE1 un enlace que permitir\\xE1\\nestablecer una nueva contrase\\xF1a.\\n\\nPREGUNTA:\\n\\n\\xBFC\\xF3mo puedo recuperar mi contrase\\xF1a?\\n\"))), mdx(\"p\", null, \"El LLM recibe la informaci\\xF3n necesaria para elaborar una respuesta.\"), mdx(\"p\", null, \"Este \\xFAltimo paso es importante.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"RAG no sustituye al modelo de lenguaje. Le proporciona informaci\\xF3n relevante antes de generar la respuesta.\")), mdx(\"h2\", null, \"Ejemplo de arquitectura RAG con NestJS y PostgreSQL\"), mdx(\"p\", null, \"Veamos ahora c\\xF3mo podr\\xEDamos incorporar este sistema a una aplicaci\\xF3n web.\"), mdx(\"p\", null, \"Supongamos que utilizamos:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"React para el frontend.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"NestJS para el backend.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"PostgreSQL como base de datos.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"pgvector para almacenar embeddings.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Una API de modelos de IA para generar embeddings y respuestas.\")), mdx(\"p\", null, \"La arquitectura podr\\xEDa organizarse as\\xED:\"), mdx(\"p\", null, mdx(\"span\", {\n    parentName: \"p\",\n    \"className\": \"gatsby-resp-image-wrapper\",\n    \"style\": {\n      \"position\": \"relative\",\n      \"display\": \"block\",\n      \"marginLeft\": \"auto\",\n      \"marginRight\": \"auto\",\n      \"maxWidth\": \"1200px\"\n    }\n  }, \"\\n      \", mdx(\"a\", {\n    parentName: \"span\",\n    \"className\": \"gatsby-resp-image-link\",\n    \"href\": \"/static/27a6c1328f413762537e67c8680f23fc/aa619/rag-arquitectura-v2.webp\",\n    \"style\": {\n      \"display\": \"block\"\n    },\n    \"target\": \"_blank\",\n    \"rel\": \"noopener\"\n  }, \"\\n    \", mdx(\"span\", {\n    parentName: \"a\",\n    \"className\": \"gatsby-resp-image-background-image\",\n    \"style\": {\n      \"paddingBottom\": \"60.66666666666667%\",\n      \"position\": \"relative\",\n      \"bottom\": \"0\",\n      \"left\": \"0\",\n      \"backgroundImage\": \"url('data:image/webp;base64,UklGRlIAAABXRUJQVlA4IEYAAACwAwCdASoUAAwAPtFWo0uoJKMhsAgBABoJaQAAW+jLj7k5+j4JgAD+9NwbeBaRaJuf4ffLhWs3TZoGFzlkm+BdQhU0AAAA')\",\n      \"backgroundSize\": \"cover\",\n      \"display\": \"block\"\n    }\n  }), \"\\n  \", mdx(\"img\", {\n    parentName: \"a\",\n    \"className\": \"gatsby-resp-image-image\",\n    \"alt\": \"Esquema de arquitectura RAG: React envía la pregunta a NestJS, EmbeddingService consulta PostgreSQL con pgvector y LlmService genera la respuesta con los fragmentos recuperados\",\n    \"title\": \"Esquema de arquitectura RAG: React envía la pregunta a NestJS, EmbeddingService consulta PostgreSQL con pgvector y LlmService genera la respuesta con los fragmentos recuperados\",\n    \"src\": \"/static/27a6c1328f413762537e67c8680f23fc/5bb53/rag-arquitectura-v2.webp\",\n    \"srcSet\": [\"/static/27a6c1328f413762537e67c8680f23fc/cb58d/rag-arquitectura-v2.webp 300w\", \"/static/27a6c1328f413762537e67c8680f23fc/f8b1b/rag-arquitectura-v2.webp 600w\", \"/static/27a6c1328f413762537e67c8680f23fc/5bb53/rag-arquitectura-v2.webp 1200w\", \"/static/27a6c1328f413762537e67c8680f23fc/aa619/rag-arquitectura-v2.webp 1400w\"],\n    \"sizes\": \"(max-width: 1200px) 100vw, 1200px\",\n    \"style\": {\n      \"width\": \"100%\",\n      \"height\": \"100%\",\n      \"margin\": \"0\",\n      \"verticalAlign\": \"middle\",\n      \"position\": \"absolute\",\n      \"top\": \"0\",\n      \"left\": \"0\"\n    },\n    \"loading\": \"lazy\"\n  }), \"\\n  \"), \"\\n    \")), mdx(\"p\", null, \"Conviene separar las responsabilidades.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"src/\\n  rag/\\n    rag.module.ts\\n    rag.controller.ts\\n    rag.service.ts\\n\\n    services/\\n      embedding.service.ts\\n      retrieval.service.ts\\n      generation.service.ts\\n\"))), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"EmbeddingService\"), \" transforma texto en vectores.\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RetrievalService\"), \" busca fragmentos relevantes.\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"GenerationService\"), \" comunica con el modelo generativo.\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RagService\"), \" coordina el proceso completo.\"), mdx(\"p\", null, \"La estructura exacta depender\\xE1 de la complejidad de la aplicaci\\xF3n, pero esta separaci\\xF3n permite cambiar componentes sin reescribir toda la soluci\\xF3n.\"), mdx(\"h2\", null, \"Implementaci\\xF3n simplificada en TypeScript\"), mdx(\"p\", null, \"Podr\\xEDamos empezar definiendo una interfaz para los fragmentos recuperados:\"), mdx(CodeJs, {\n    language: \"typescript\",\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-typescript\"\n  }, \"interface RetrievedChunk {\\n  id: number;\\n  documentId: number;\\n  content: string;\\n  score?: number;\\n}\\n\"))), mdx(\"p\", null, \"Despu\\xE9s implementamos un servicio que coordine la consulta:\"), mdx(CodeJs, {\n    language: \"typescript\",\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-typescript\"\n  }, \"@Injectable()\\nexport class RagService {\\n  constructor(\\n    private readonly embeddingService: EmbeddingService,\\n    private readonly retrievalService: RetrievalService,\\n    private readonly generationService: GenerationService,\\n  ) {}\\n\\n  async ask(question: string, userId: string) {\\n    const embedding =\\n      await this.embeddingService.embed(question);\\n\\n    const chunks =\\n      await this.retrievalService.search({\\n        embedding,\\n        userId,\\n        limit: 5,\\n      });\\n\\n    const context = chunks\\n      .map((chunk, index) => {\\n        return `[${index + 1}] ${chunk.content}`;\\n      })\\n      .join('\\\\n\\\\n');\\n\\n    const answer =\\n      await this.generationService.generate({\\n        question,\\n        context,\\n      });\\n\\n    return {\\n      answer,\\n      sources: chunks.map((chunk) => ({\\n        documentId: chunk.documentId,\\n        chunkId: chunk.id,\\n      })),\\n    };\\n  }\\n}\\n\"))), mdx(\"p\", null, \"Este c\\xF3digo es conceptual: los servicios auxiliares deben implementarse, y el buscador debe aplicar los permisos de acceso antes de devolver documentos.\"), mdx(\"p\", null, \"La arquitectura, sin embargo, refleja una separaci\\xF3n importante.\"), mdx(\"p\", null, \"El proceso de recuperaci\\xF3n de informaci\\xF3n y el proceso de generaci\\xF3n son responsabilidades distintas.\"), mdx(\"p\", null, \"Podemos sustituir PostgreSQL por otro motor de b\\xFAsqueda.\"), mdx(\"p\", null, \"Tambi\\xE9n podemos cambiar el proveedor del LLM sin alterar necesariamente el sistema de almacenamiento.\"), mdx(\"h2\", null, \"Un RAG no necesita utilizar siempre b\\xFAsqueda vectorial\"), mdx(\"p\", null, \"Hasta ahora hemos descrito una implementaci\\xF3n basada en embeddings.\"), mdx(\"p\", null, \"Es una arquitectura habitual, pero no la \\xFAnica.\"), mdx(\"p\", null, \"La recuperaci\\xF3n de informaci\\xF3n tambi\\xE9n puede utilizar:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"b\\xFAsqueda por palabras clave;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"b\\xFAsqueda de texto completo;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"filtros estructurados;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"consultas SQL;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"b\\xFAsqueda h\\xEDbrida;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"sistemas especializados de recuperaci\\xF3n.\")), mdx(\"p\", null, \"Por ejemplo, si un usuario pregunta:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"\\xBFQu\\xE9 significa el error AUTH_4037?\")), mdx(\"p\", null, \"Puede que una b\\xFAsqueda exacta resulte m\\xE1s eficaz que una b\\xFAsqueda sem\\xE1ntica.\"), mdx(\"p\", null, \"En cambio:\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"\\xBFPor qu\\xE9 no puedo acceder a mi cuenta?\")), mdx(\"p\", null, \"puede beneficiarse de la b\\xFAsqueda vectorial.\"), mdx(\"p\", null, \"Por eso muchos sistemas RAG modernos utilizan \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"b\\xFAsqueda h\\xEDbrida\"), \", combinando b\\xFAsquedas textuales y vectoriales.\"), mdx(\"p\", null, \"En algunos casos tambi\\xE9n incorporan una etapa de \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"reranking\"), \" que vuelve a ordenar los documentos recuperados seg\\xFAn su relevancia para la pregunta.\"), mdx(\"p\", null, \"Esto mejora potencialmente la precisi\\xF3n, aunque a\\xF1ade complejidad, coste y latencia.\"), mdx(\"h2\", null, \"\\xBFQu\\xE9 diferencia hay entre RAG y fine-tuning?\"), mdx(\"p\", null, \"Esta es una de las dudas m\\xE1s frecuentes cuando comenzamos a desarrollar aplicaciones con IA.\"), mdx(\"p\", null, \"RAG y fine-tuning permiten adaptar el comportamiento de una soluci\\xF3n basada en LLM, pero no resuelven exactamente el mismo problema.\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Caracter\\xEDstica\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"RAG\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Fine-tuning\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Objetivo principal\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Aportar informaci\\xF3n externa al modelo\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Adaptar el comportamiento del modelo\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Actualizar documentos\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Modificar y reindexar la informaci\\xF3n\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"No se actualiza autom\\xE1ticamente\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Conocimiento privado\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Recuperado durante la consulta\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Depende de los datos y objetivos del entrenamiento\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Reentrenamiento\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"No es necesario\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Requiere un proceso de entrenamiento\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Referencias a fuentes\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Se pueden incorporar\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"No aparecen autom\\xE1ticamente\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Casos habituales\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Documentaci\\xF3n, soporte, b\\xFAsqueda\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Formatos, estilos y tareas especializadas\")))), mdx(\"p\", null, \"Imaginemos dos necesidades diferentes.\"), mdx(\"p\", null, \"La primera consiste en responder preguntas sobre documentos internos que cambian frecuentemente.\"), mdx(\"p\", null, \"RAG suele ser una alternativa adecuada.\"), mdx(\"p\", null, \"La segunda consiste en conseguir que un modelo produzca respuestas con un formato muy espec\\xEDfico o realice una tarea especializada de manera consistente.\"), mdx(\"p\", null, \"En determinados casos, el fine-tuning puede resultar \\xFAtil.\"), mdx(\"p\", null, \"Ambas t\\xE9cnicas tambi\\xE9n pueden combinarse.\"), mdx(\"p\", null, \"No son necesariamente excluyentes.\"), mdx(\"h2\", null, \"Las alucinaciones siguen siendo un problema\"), mdx(\"p\", null, \"Uno de los argumentos habituales a favor de RAG es que reduce las respuestas inventadas por los modelos.\"), mdx(\"p\", null, \"Esto puede ser cierto cuando el sistema recupera informaci\\xF3n relevante y el modelo la utiliza correctamente.\"), mdx(\"p\", null, \"Pero conviene evitar una conclusi\\xF3n equivocada.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"RAG no elimina las alucinaciones.\")), mdx(\"p\", null, \"Un sistema puede recuperar documentos incorrectos.\"), mdx(\"p\", null, \"Puede encontrar fragmentos desactualizados.\"), mdx(\"p\", null, \"Puede seleccionar contenido poco relevante.\"), mdx(\"p\", null, \"O el LLM puede interpretar incorrectamente la informaci\\xF3n recibida.\"), mdx(\"p\", null, \"Incluso podr\\xEDa generar una respuesta que no est\\xE9 respaldada por las fuentes recuperadas.\"), mdx(\"p\", null, \"Por eso resulta importante establecer reglas de generaci\\xF3n y mecanismos de verificaci\\xF3n.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Utiliza \\xFAnicamente la informaci\\xF3n del contexto.\\n\\nNo inventes procedimientos ni requisitos.\\n\\nSi no encuentras informaci\\xF3n suficiente,\\nindica que no puedes responder con seguridad.\\n\\nIncluye las referencias a los documentos\\nque respalden la respuesta.\\n\"))), mdx(\"p\", null, \"Estas instrucciones ayudan, pero no garantizan el comportamiento del modelo.\"), mdx(\"p\", null, \"En sistemas cr\\xEDticos debemos a\\xF1adir validaciones y pruebas espec\\xEDficas.\"), mdx(\"h2\", null, \"La seguridad es especialmente importante en aplicaciones multiempresa\"), mdx(\"p\", null, \"Imaginemos que estamos construyendo un SaaS utilizado por m\\xFAltiples empresas.\"), mdx(\"p\", null, \"Cada empresa puede incorporar sus propios documentos.\"), mdx(\"p\", null, \"En ese escenario, una b\\xFAsqueda vectorial sin restricciones podr\\xEDa recuperar accidentalmente informaci\\xF3n perteneciente a otra organizaci\\xF3n.\"), mdx(\"p\", null, \"No basta con indicar al LLM:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"No muestres documentos de otras empresas.\\n\"))), mdx(\"p\", null, \"La separaci\\xF3n debe producirse antes de enviar los fragmentos al modelo.\"), mdx(\"p\", null, \"Por ejemplo, podr\\xEDamos incorporar un identificador de empresa:\"), mdx(CodeJs, {\n    language: \"sql\",\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-sql\"\n  }, \"CREATE TABLE document_chunks (\\n    id BIGSERIAL PRIMARY KEY,\\n    company_id BIGINT NOT NULL,\\n    document_id BIGINT NOT NULL,\\n    content TEXT NOT NULL,\\n    embedding vector(1536)\\n);\\n\"))), mdx(\"p\", null, \"Y filtrar las consultas:\"), mdx(CodeJs, {\n    language: \"sql\",\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-sql\"\n  }, \"SELECT\\n    id,\\n    document_id,\\n    content\\nFROM document_chunks\\nWHERE company_id = $1\\nORDER BY embedding <=> $2::vector\\nLIMIT 5;\\n\"))), mdx(\"p\", null, \"El identificador de empresa debe obtenerse del contexto de autenticaci\\xF3n autorizado, no confiarse directamente a un par\\xE1metro enviado por el cliente.\"), mdx(\"p\", null, \"Si existen permisos por documento, tambi\\xE9n deber\\xE1n aplicarse antes de recuperar el contenido.\"), mdx(\"p\", null, \"Un \\xEDndice vectorial no sustituye al sistema de autorizaci\\xF3n de una aplicaci\\xF3n.\"), mdx(\"p\", null, \"Tambi\\xE9n debemos recordar que los propios documentos pueden contener instrucciones maliciosas.\"), mdx(\"p\", null, \"Un fragmento recuperado es informaci\\xF3n, no una orden que el agente deba obedecer.\"), mdx(\"p\", null, \"Por tanto, debemos proteger el sistema frente a ataques de \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"prompt injection\"), \", especialmente cuando los documentos proceden de fuentes externas o pueden ser modificados por usuarios.\"), mdx(\"h2\", null, \"\\xBFC\\xF3mo se mide la calidad de un RAG?\"), mdx(\"p\", null, \"Un error habitual consiste en evaluar el sistema realizando unas cuantas preguntas y comprobando si las respuestas parecen razonables.\"), mdx(\"p\", null, \"Eso puede servir durante las primeras pruebas, pero no es suficiente para una aplicaci\\xF3n de producci\\xF3n.\"), mdx(\"p\", null, \"Hay que evaluar al menos dos componentes diferentes.\"), mdx(\"h3\", null, \"Calidad de recuperaci\\xF3n\"), mdx(\"p\", null, \"\\xBFHemos encontrado los documentos correctos?\"), mdx(\"p\", null, \"Podemos utilizar m\\xE9tricas como:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Recall@K:\"), \" proporci\\xF3n de resultados relevantes recuperados entre los primeros K documentos.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Precision@K:\"), \" proporci\\xF3n de resultados relevantes dentro de esos K documentos.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"MRR:\"), \" mide la posici\\xF3n del primer resultado relevante.\")), mdx(\"h3\", null, \"Calidad de generaci\\xF3n\"), mdx(\"p\", null, \"\\xBFLa respuesta utiliza correctamente la informaci\\xF3n recuperada?\"), mdx(\"p\", null, \"Aqu\\xED podemos evaluar:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"relevancia de la respuesta;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"exactitud respecto a las fuentes;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"ausencia de afirmaciones no respaldadas;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"correcci\\xF3n de las referencias;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"capacidad para reconocer informaci\\xF3n insuficiente.\")), mdx(\"p\", null, \"Tambi\\xE9n debemos medir caracter\\xEDsticas operativas:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Latencia de embeddings\\n+\\nLatencia de b\\xFAsqueda\\n+\\nLatencia del LLM\\n=\\nLatencia total\\n\"))), mdx(\"p\", null, \"Y, naturalmente, el coste.\"), mdx(\"p\", null, \"No tiene sentido implementar un sistema extremadamente sofisticado si tarda veinte segundos en responder preguntas sencillas y cada consulta resulta demasiado cara para nuestro modelo de negocio.\"), mdx(\"h2\", null, \"\\xBFCu\\xE1ndo merece la pena implementar un RAG?\"), mdx(\"p\", null, \"RAG resulta especialmente interesante cuando necesitamos que una aplicaci\\xF3n responda preguntas utilizando informaci\\xF3n externa al modelo.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(\"h3\", null, \"Asistentes de soporte t\\xE9cnico\"), mdx(\"p\", null, \"Un chatbot puede consultar manuales, preguntas frecuentes o documentaci\\xF3n del producto.\"), mdx(\"h3\", null, \"Aplicaciones empresariales\"), mdx(\"p\", null, \"Los empleados pueden consultar pol\\xEDticas, procedimientos y documentaci\\xF3n interna mediante lenguaje natural.\"), mdx(\"h3\", null, \"Plataformas educativas\"), mdx(\"p\", null, \"Los alumnos pueden realizar preguntas sobre materiales de un curso y recibir respuestas basadas en los contenidos.\"), mdx(\"h3\", null, \"Sistemas de documentaci\\xF3n para desarrolladores\"), mdx(\"p\", null, \"Podemos construir asistentes capaces de responder preguntas sobre una API, una arquitectura o una colecci\\xF3n de documentos t\\xE9cnicos.\"), mdx(\"h3\", null, \"Aplicaciones SaaS\"), mdx(\"p\", null, \"Cada organizaci\\xF3n puede disponer de una base de conocimiento privada para sus usuarios autorizados.\"), mdx(\"p\", null, \"Sin embargo, no siempre necesitamos RAG.\"), mdx(\"p\", null, \"Si una aplicaci\\xF3n \\xFAnicamente debe consultar el saldo de una cuenta o recuperar una factura por su identificador, probablemente resulte m\\xE1s adecuado realizar una consulta SQL convencional o utilizar una herramienta de acceso a datos estructurados.\"), mdx(\"p\", null, \"Introducir embeddings y b\\xFAsqueda vectorial en ese escenario puede a\\xF1adir complejidad sin aportar valor.\"), mdx(\"h2\", null, \"\\xBFConstruir nuestro propio RAG o utilizar un servicio gestionado?\"), mdx(\"p\", null, \"Actualmente existen dos aproximaciones principales.\"), mdx(\"p\", null, \"La primera consiste en construir los componentes de recuperaci\\xF3n de forma independiente.\"), mdx(\"p\", null, \"Por ejemplo:\"), mdx(CodeJs, {\n    mdxType: \"CodeJs\"\n  }, mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"NestJS\\n   +\\nPostgreSQL\\n   +\\npgvector\\n   +\\nAPI de embeddings\\n   +\\nLLM\\n\"))), mdx(\"p\", null, \"Esto proporciona control sobre el almacenamiento, los permisos, la indexaci\\xF3n y la estrategia de recuperaci\\xF3n.\"), mdx(\"p\", null, \"La segunda consiste en utilizar un servicio gestionado que incorpore parte de estas operaciones.\"), mdx(\"p\", null, \"Por ejemplo, OpenAI ofrece herramientas de recuperaci\\xF3n y b\\xFAsqueda sobre archivos mediante almacenes vectoriales.\"), mdx(\"p\", null, \"En estos sistemas parte del trabajo de fragmentaci\\xF3n, indexaci\\xF3n y recuperaci\\xF3n lo realiza el proveedor.\"), mdx(\"p\", null, \"La elecci\\xF3n depende de nuestras necesidades.\"), mdx(\"p\", null, \"Si queremos construir r\\xE1pidamente un prototipo, un servicio gestionado puede resultar conveniente.\"), mdx(\"p\", null, \"Si necesitamos controlar detalladamente los datos, los permisos, la infraestructura o los costes de almacenamiento, una implementaci\\xF3n propia puede ser m\\xE1s adecuada.\"), mdx(\"p\", null, \"Tambi\\xE9n debemos tener en cuenta las condiciones de tratamiento de datos cuando trabajamos con informaci\\xF3n privada o confidencial.\"), mdx(\"h2\", null, \"Conclusi\\xF3n: RAG conecta los LLM con los datos de nuestras aplicaciones\"), mdx(\"p\", null, \"RAG es una arquitectura que permite complementar el conocimiento de un modelo de lenguaje con informaci\\xF3n recuperada de fuentes externas.\"), mdx(\"p\", null, \"Su funcionamiento combina conceptos que los desarrolladores ya conocemos:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"procesamiento de documentos;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"indexaci\\xF3n;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"bases de datos;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"b\\xFAsqueda de informaci\\xF3n;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"APIs;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"servicios backend;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"control de acceso.\")), mdx(\"p\", null, \"La principal novedad est\\xE1 en c\\xF3mo conectamos esas piezas con los modelos de lenguaje.\"), mdx(\"p\", null, \"Los embeddings permiten recuperar documentos por similitud sem\\xE1ntica.\"), mdx(\"p\", null, \"Las bases de datos vectoriales facilitan ese tipo de consultas.\"), mdx(\"p\", null, \"Y los LLM utilizan los fragmentos encontrados para construir respuestas en lenguaje natural.\"), mdx(\"p\", null, \"Pero el verdadero desaf\\xEDo no consiste \\xFAnicamente en conectar una base de datos con una API de inteligencia artificial.\"), mdx(\"p\", null, \"Consiste en recuperar la informaci\\xF3n correcta, respetar los permisos, controlar los costes, evaluar los resultados y evitar que el modelo presente informaci\\xF3n inventada como si fuera cierta.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Un RAG bien dise\\xF1ado no convierte a un LLM en una fuente infalible. Lo convierte en una interfaz m\\xE1s \\xFAtil para consultar informaci\\xF3n que nuestra aplicaci\\xF3n ya posee.\")), mdx(\"p\", null, \"Y precisamente por eso RAG se ha convertido en una pieza importante de la arquitectura de muchas aplicaciones que incorporan inteligencia artificial.\"), mdx(\"h2\", null, \"Fuentes y documentaci\\xF3n\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://arxiv.org/abs/2005.11401\"\n  }, \"Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks \\u2014 Lewis et al.\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://developers.openai.com/api/docs/guides/retrieval\"\n  }, \"OpenAI \\u2014 Retrieval API\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://developers.openai.com/api/docs/guides/tools-file-search\"\n  }, \"OpenAI \\u2014 File Search\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://www.microsoft.com/en-us/microsoft-cloud/blog/2025/02/04/common-retrieval-augmented-generation-rag-techniques-explained/\"\n  }, \"Microsoft \\u2014 Common Retrieval Augmented Generation Techniques\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://learn.microsoft.com/en-us/azure/architecture/ai-ml/guide/rag/rag-information-retrieval\"\n  }, \"Microsoft Learn \\u2014 RAG Information Retrieval\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://github.com/pgvector/pgvector\"\n  }, \"pgvector \\u2014 Extensi\\xF3n vectorial para PostgreSQL\"))));\n}\n;\nMDXContent.isMDXComponent = true;","frontmatter":{"twitterUsername":"@CursosDesarrol1","title":"Qué es RAG en inteligencia artificial y cómo integrarlo en una aplicación web","seoTitle":null,"date":"2026-10-28","description":"Descubre qué es RAG, cómo funcionan los embeddings y las bases de datos vectoriales y cómo integrar esta arquitectura en una aplicación web con NestJS y PostgreSQL.","featuredImage":{"childImageSharp":{"fluid":{"base64":"data:image/webp;base64,UklGRn4AAABXRUJQVlA4IHIAAADQAwCdASoUAAkAPtFUo0uoJKMhsAgBABoJQBOiP/wGJJ2C+NTeVAAA/u3uGpX5G0cpfR3x9JLB/bVThhFdvfkWOmiFC2NwXJYttGXPFd8bjqqf5URWo0pV/8oAehkLUQ4lygAgxhMqdovsEf5AryCAAAA=","aspectRatio":2.2988505747126435,"src":"/static/55cfbb00f1817386da756c2a87abe1c8/ccdb5/rag-portada.webp","srcSet":"/static/55cfbb00f1817386da756c2a87abe1c8/6b183/rag-portada.webp 200w,\n/static/55cfbb00f1817386da756c2a87abe1c8/fc32b/rag-portada.webp 400w,\n/static/55cfbb00f1817386da756c2a87abe1c8/ccdb5/rag-portada.webp 800w,\n/static/55cfbb00f1817386da756c2a87abe1c8/9000d/rag-portada.webp 1200w","sizes":"(max-width: 800px) 100vw, 800px"}}}}}},"pageContext":{"slug":"/articulos/que-es-rag-inteligencia-artificial/","previous":{"fields":{"slug":"/articulos/organic-driven-development-odd/"}},"next":{"fields":{"slug":"/avisos_legales/aviso_legal/"}}}},"staticQueryHashes":["2032873950"]}